/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.loader;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.jianggujin.modulelink.action.JAction;
import com.jianggujin.modulelink.config.JModuleConfig;
import com.jianggujin.modulelink.config.JSpringBootModuleConfigAdapter;
import com.jianggujin.modulelink.config.JSpringModuleConfig;
import com.jianggujin.modulelink.config.JSpringModuleConfigAdapter;
import com.jianggujin.modulelink.exception.JModuleLinkException;
import com.jianggujin.modulelink.magager.JModuleManagerSupport;
import com.jianggujin.modulelink.module.JDefaultModule;
import com.jianggujin.modulelink.module.JModule;
import com.jianggujin.modulelink.module.JModuleListener;
import com.jianggujin.modulelink.support.JDisposable;
import com.jianggujin.modulelink.support.annotation.JActionComponent;
import com.jianggujin.modulelink.support.annotation.JExclusion;
import com.jianggujin.modulelink.support.plugin.JInterceptor;
import com.jianggujin.modulelink.util.JAssert;
import com.jianggujin.modulelink.util.JBeanUtils;
import com.jianggujin.modulelink.util.JLogFactory;
import com.jianggujin.modulelink.util.JLogFactory.JLog;
import com.jianggujin.modulelink.util.JModuleClassLoader;
import com.jianggujin.modulelink.util.JModuleUtils;

/**
 * Spring模块加载器解析器实现
 * 
 * @author jianggujin
 *
 */
public class JSpringModuleLoaderSolver extends JAbstractModuleLoaderSolver {
    private static final JLog logger = JLogFactory.getLog(JSpringModuleLoaderSolver.class);
    /**
     * Spring bean文件所在目录,不同的路径确保能取到资源
     */
    private final static String[] SPRING_XML_PATTERN = { "classpath*:META-INF/spring/*.xml",
            "classpath*:*META-INF/spring/*.xml" };
    private final static String CLASS_PATH_XML_APPLICATION_CONTEXT = "org.springframework.context.support.ClassPathXmlApplicationContext";
    private final static String ANNOTATION_CONFIG_APPLICATION_CONTEXT = "org.springframework.context.annotation.AnnotationConfigApplicationContext";
    private final static String PATH_MATCHING_RESOURCE_PATTERN_RESOLVER = "org.springframework.core.io.support.PathMatchingResourcePatternResolver";
    private final static String RESOURCE = "org.springframework.core.io.Resource";

    private final static String SPRING_APPLICATION = "org.springframework.boot.SpringApplication";

    @Override
    public <T extends JModuleConfig> JModule<T> load(T moduleConfig, JModuleClassLoader moduleClassLoader,
            JModuleManagerSupport<T> moduleManager) throws Exception {
        Object applicationContext = createSpringApplicationContext(moduleConfig, moduleClassLoader);
        Class<?> contextClass = applicationContext.getClass();
        Method getBeansOfTypeMethod = contextClass.getMethod("getBeansOfType", Class.class);

        List<JModuleListener> listeners = this.loadModuleListeners(moduleConfig, moduleManager, applicationContext,
                getBeansOfTypeMethod);
        JDefaultModule<T> module = this.createModule(moduleManager, moduleConfig, moduleClassLoader, listeners);
        module.setDisposable(new JApplicationContextDisposable((Closeable) applicationContext));

        List<JInterceptor> globalInterceptors = moduleManager.getInterceptors();
        List<JInterceptor> interceptors = this.loadInterceptors(moduleConfig, applicationContext, getBeansOfTypeMethod);
        if (interceptors == null) {
            interceptors = globalInterceptors;
        } else if (globalInterceptors != null) {
            interceptors.addAll(globalInterceptors);
        }

        Set<Object> actions = this.loadActions(moduleConfig, applicationContext, getBeansOfTypeMethod);
        this.initActions(moduleManager, interceptors, module, actions);

        module.initialize();
        return module;
    }

    /**
     * 加载模块监听器
     * 
     * @param moduleConfig
     * @param moduleManager
     * @param applicationContext
     * @param getBeansOfTypeMethod
     * @return
     * @throws Exception
     */
    protected List<JModuleListener> loadModuleListeners(JModuleConfig moduleConfig,
            JModuleManagerSupport<?> moduleManager, Object applicationContext, Method getBeansOfTypeMethod)
            throws Exception {
        List<JModuleListener> listeners = new ArrayList<JModuleListener>();
        // 查找模块监听器
        List<JModuleListener> moduleListeners = moduleManager.getModuleListeners();
        if (moduleListeners != null) {
            listeners.addAll(moduleListeners);
        }
        listeners.addAll(this.scan(applicationContext, getBeansOfTypeMethod, moduleConfig.getExclusionModuleListeners(),
                JModuleListener.class));
        return listeners.isEmpty() ? null : Collections.unmodifiableList(listeners);
    }

    /**
     * 加载模块拦截器，不包含全局拦截器
     * 
     * @param moduleConfig
     * @param applicationContext
     * @param getBeansOfTypeMethod
     * @return
     * @throws Exception
     */
    protected List<JInterceptor> loadInterceptors(JModuleConfig moduleConfig, Object applicationContext,
            Method getBeansOfTypeMethod) throws Exception {
        List<JInterceptor> interceptors = this.scan(applicationContext, getBeansOfTypeMethod,
                moduleConfig.getExclusionInterceptors(), JInterceptor.class);
        JModuleUtils.sortOrder(interceptors);
        return interceptors;
    }

    protected Set<Object> loadActions(JModuleConfig moduleConfig, Object applicationContext,
            Method getBeansOfTypeMethod) throws Exception {
        Set<Object> results = new HashSet<Object>();
        Set<String> exclusions = moduleConfig.getExclusionActions();
        if (exclusions == null) {
            exclusions = Collections.emptySet();
        }

        @SuppressWarnings("unchecked")
        Map<String, JAction> actions = (Map<String, JAction>) getBeansOfTypeMethod.invoke(applicationContext,
                JAction.class);
        for (JAction value : actions.values()) {
            Class<?> cls = value.getClass();
            if (!cls.isAnnotationPresent(JExclusion.class) && !exclusions.contains(cls.getName())) {
                results.add(value);
            }
        }

        Class<?> contextClass = applicationContext.getClass();
        Method getBeansWithAnnotation = contextClass.getMethod("getBeansWithAnnotation", Class.class);
        @SuppressWarnings("unchecked")
        Map<String, Object> actionComponents = (Map<String, Object>) getBeansWithAnnotation.invoke(applicationContext,
                JActionComponent.class);
        for (Object value : actionComponents.values()) {
            Class<?> cls = value.getClass();
            if (!cls.isAnnotationPresent(JExclusion.class) && !exclusions.contains(cls.getName())) {
                results.add(value);
            }
        }
        return results;
    }

    /**
     * 查找指定类型
     * 
     * @param <T>
     * @param applicationContext
     * @param getBeansOfTypeMethod
     * @param exclusions
     * @param clazz
     * @return
     * @throws Exception
     */
    protected <T> List<T> scan(Object applicationContext, Method getBeansOfTypeMethod, Set<String> exclusions,
            Class<T> clazz) throws Exception {
        if (exclusions == null) {
            exclusions = Collections.emptySet();
        }
        List<T> results = new ArrayList<T>();
        @SuppressWarnings("unchecked")
        Map<String, T> allResults = (Map<String, T>) getBeansOfTypeMethod.invoke(applicationContext, clazz);
        for (T value : allResults.values()) {
            Class<?> cls = value.getClass();
            if (!cls.isAnnotationPresent(JExclusion.class) && !exclusions.contains(cls.getName())) {
                results.add(value);
            }
        }
        return results;
    }

    @Override
    public boolean matches(JModuleConfig moduleConfig, JModuleClassLoader moduleClassLoader) {
        // 配置既不是Spring也不是SpringBoot
        if (!(moduleConfig instanceof JSpringModuleConfigAdapter
                || moduleConfig instanceof JSpringBootModuleConfigAdapter)) {
            return false;
        }
        if (isSpringBoot(moduleConfig, moduleClassLoader)) {
            return true;
        }
        try {
            moduleClassLoader.loadClass(CLASS_PATH_XML_APPLICATION_CONTEXT);
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    /**
     * 是否为SpringBoot模块
     * 
     * @param moduleConfig
     * @param moduleClassLoader
     * @return
     */
    private boolean isSpringBoot(JModuleConfig moduleConfig, JModuleClassLoader moduleClassLoader) {
        if (moduleConfig instanceof JSpringBootModuleConfigAdapter) {
            try {
                moduleClassLoader.loadClass(SPRING_APPLICATION);
                return true;
            } catch (ClassNotFoundException e) {
            }
        }
        return false;
    }

    /**
     * 创建Spring上下文
     * 
     * @param moduleConfig      模块配置
     * @param moduleClassLoader 模块类加载器
     * @return Spring上下文，根据配置对象创建XML或注解上下文
     * @throws Exception 创建上下文对象出现异常
     */
    private Object createSpringApplicationContext(JModuleConfig moduleConfig, JModuleClassLoader moduleClassLoader)
            throws Exception {
        if (isSpringBoot(moduleConfig, moduleClassLoader)) {
            return this.createSpringBootApplicationContext(((JSpringBootModuleConfigAdapter) moduleConfig).getSources(),
                    moduleClassLoader);
        }
        Object context = null;
        Set<String> scanBase = moduleConfig.getScanPackages();
        try {
            if (scanBase == null || scanBase.isEmpty()) {
                context = createXMLAppicationContext(moduleConfig, moduleClassLoader);
            } else {
                context = createAnnotationAppicationContext(moduleConfig, moduleClassLoader);
            }
            Method refreshMethod = JBeanUtils.findMethod(context.getClass(), "refresh");
            JBeanUtils.invokeMethod(context, refreshMethod);
            // context.getClass().getMethod("refresh").invoke(context);
        } catch (Exception e) {
            this.closeContext(context);
            throw e;
        }
        return context;
    }

    /**
     * 创建SpringBoot上下文
     * 
     * @param sources           SpringBoot sources
     * @param moduleClassLoader 模块类加载器
     * @return SpringBoot创建的Spring上下文
     * @throws Exception 创建上下文对象出现异常
     */
    private Object createSpringBootApplicationContext(Set<String> sources, ClassLoader moduleClassLoader)
            throws Exception {
        Object context = null;
        try {
            Class<?> springApplicationClass = moduleClassLoader.loadClass(SPRING_APPLICATION);
            Method runMethod = JBeanUtils.findMethod(springApplicationClass, "run", Object[].class, String[].class);
            // springApplicationClass.getMethod("run", Object[].class, String[].class);
            if (sources == null) {
                context = JBeanUtils.invokeMethod(null, runMethod, new Object[0], new String[0]);
                // runMethod.invoke(null, new Object[0], new String[0]);
            } else {
                Object[] sours = new Object[sources.size()];
                int index = 0;
                for (String source : sources) {
                    sours[index] = moduleClassLoader.loadClass(source);
                    index++;
                }
                context = JBeanUtils.invokeMethod(null, runMethod, sours, new String[0]);
                // runMethod.invoke(null, sours, new String[0]);
            }
        } catch (Exception e) {
            this.closeContext(context);
            throw e;
        }
        return context;
    }

    /**
     * 关闭Spring上下文
     * 
     * @param context
     */
    private void closeContext(Object context) {
        if (context != null && context instanceof Closeable) {
            try {
                ((Closeable) context).close();
            } catch (Exception ex) {
                logger.error("failed to close application context", ex);
            }
        }
    }

    /**
     * 创建Spring XML应用上下文
     * 
     * @param moduleConfig      模块配置
     * @param moduleClassLoader 模块类加载器
     * @return Spring XML应用上下文
     * @throws Exception 创建上下文对象出现异常
     */
    private Object createXMLAppicationContext(JModuleConfig moduleConfig, JModuleClassLoader moduleClassLoader)
            throws Exception {
        Class<?> classPathXmlApplicationContextClass = moduleClassLoader.loadClass(CLASS_PATH_XML_APPLICATION_CONTEXT);
        Object context = classPathXmlApplicationContextClass.newInstance();
        JSpringModuleConfigAdapter config = null;
        if (moduleConfig instanceof JSpringModuleConfigAdapter) {
            config = (JSpringModuleConfigAdapter) moduleConfig;
        } else {
            config = new JSpringModuleConfig(moduleConfig);
        }
        String[] locations = this.findSpringConfigs(config, moduleClassLoader);
        // 单一可变参数，在使用反射的时候需要使用Object数组包裹，否则会类型不匹配，多个参数时不需要
        Method setConfigLocationsMethod = JBeanUtils.findMethod(classPathXmlApplicationContextClass,
                "setConfigLocations", String[].class);
        JBeanUtils.invokeMethod(context, setConfigLocationsMethod, new Object[] { locations });
        // classPathXmlApplicationContextClass.getMethod("setConfigLocations",
        // String[].class).invoke(context,
        // new Object[] { locations });
        Method setClassLoaderMethod = JBeanUtils.findMethod(classPathXmlApplicationContextClass, "setClassLoader",
                ClassLoader.class);
        JBeanUtils.invokeMethod(context, setClassLoaderMethod, moduleClassLoader);
        // classPathXmlApplicationContextClass.getMethod("setClassLoader",
        // ClassLoader.class).invoke(context,
        // moduleClassLoader);
        return context;
    }

    /**
     * 创建Spring注解应用上下文
     * 
     * @param moduleConfig      模块配置
     * @param moduleClassLoader 模块类加载器
     * @return Spring注解应用上下文
     * @throws Exception 创建上下文对象出现异常
     */
    private Object createAnnotationAppicationContext(JModuleConfig moduleConfig, JModuleClassLoader moduleClassLoader)
            throws Exception {
        Class<?> annotationConfigApplicationContextClass = moduleClassLoader
                .loadClass(ANNOTATION_CONFIG_APPLICATION_CONTEXT);

        Object context = annotationConfigApplicationContextClass.newInstance();
        Method setClassLoaderMethod = JBeanUtils.findMethod(annotationConfigApplicationContextClass, "setClassLoader",
                ClassLoader.class);
        JBeanUtils.invokeMethod(context, setClassLoaderMethod, moduleClassLoader);
        // annotationConfigApplicationContextClass.getMethod("setClassLoader",
        // ClassLoader.class).invoke(context,
        // moduleClassLoader);
        Method scanMethod = JBeanUtils.findMethod(annotationConfigApplicationContextClass, "scan", String[].class);
        JBeanUtils.invokeMethod(context, scanMethod,
                new Object[] { moduleConfig.getScanPackages().toArray(new String[0]) });
        // annotationConfigApplicationContextClass.getMethod("scan",
        // String[].class).invoke(context,
        // new Object[] { moduleConfig.getScanPackages().toArray(new String[0]) });
        return context;
    }

    /**
     * 查找资源(JAR)中的Spring配置文件
     * 
     * @param moduleConfig      模块配置
     * @param moduleClassLoader 模块类加载器
     * @return 配置文件地址
     * @throws Exception 查找配置文件出现异常
     */
    private String[] findSpringConfigs(JSpringModuleConfigAdapter moduleConfig, JModuleClassLoader moduleClassLoader)
            throws Exception {
        List<String> moduleUrlPath = moduleConfig.getModuleUrlPath();
        if (moduleUrlPath == null || moduleUrlPath.isEmpty()) {
            return new String[0];
        }
        try {
            Class<?> resolverClass = moduleClassLoader.loadClass(PATH_MATCHING_RESOURCE_PATTERN_RESOLVER);
            Object pmr = resolverClass.getConstructor(ClassLoader.class).newInstance(moduleClassLoader);
            List<Object> list = new ArrayList<Object>();
            Collection<String> xmlPatterns = moduleConfig.getXmlPatterns();
            Method getResourcesMethod = JBeanUtils.findMethod(resolverClass, "getResources", String.class);
            // resolverClass.getMethod("getResources", String.class);
            if (xmlPatterns == null || xmlPatterns.isEmpty()) {
                xmlPatterns = Arrays.asList(SPRING_XML_PATTERN);
            }
            for (String locationPattern : xmlPatterns) {
                Object result = JBeanUtils.invokeMethod(pmr, getResourcesMethod, locationPattern);
                // getResourcesMethod.invoke(pmr, locationPattern);
                if (result != null) {
                    int len = Array.getLength(result);
                    for (int i = 0; i < len; i++) {
                        list.add(Array.get(result, i));
                    }
                }
            }
            JAssert.checkArgument(list.size() > 0, "resources length is 0");

            // 因为ClassLoader是树形结构，这里会找到ModuleClassLoader以及其父类中所有符合规范的spring配置文件，所以这里需要过滤，只需要ModuleJar中的
            return this.filterURLsIncludedResources(moduleUrlPath, list, moduleConfig.getExclusions(),
                    moduleClassLoader);
        } catch (IOException e) {
            throw new IllegalStateException("failed to find spring configs from " + moduleUrlPath, e);
        }
    }

    /**
     * 过滤查找到的spring配置文件资源，只查找模块中的spring配置文件
     * 
     * @param moduleUrlPath            模块地址
     * @param resources                Spring中的Resource对象集合
     * @param exclusionConfigeNameList 需要排除的配置文件名称
     * @param moduleClassLoader        模块类加载器
     * @return 配置文件地址
     * @throws Exception 过滤配置文件出现异常
     */
    private String[] filterURLsIncludedResources(List<String> moduleUrlPath, List<Object> resources,
            Set<String> exclusionConfigeNameList, ClassLoader moduleClassLoader) throws Exception {
        List<String> configLocations = new ArrayList<String>();
        boolean exclusion = exclusionConfigeNameList != null && !exclusionConfigeNameList.isEmpty();
        Class<?> resourceClass = moduleClassLoader.loadClass(RESOURCE);
        Method getURLMethod = JBeanUtils.findMethod(resourceClass, "getURL");
        // resourceClass.getMethod("getURL");
        for (Object resource : resources) {
            String configLocation = JBeanUtils.invokeMethod(resource, getURLMethod).toString();
            // getURLMethod.invoke(resource).toString();
            for (String url : moduleUrlPath) {
                if (exclusion && this.isExclusionConfig(configLocation, exclusionConfigeNameList)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("exclusion url: " + configLocation);
                    }
                    continue;
                }
                if (configLocation.contains(url)) {
                    configLocations.add(configLocation);
                }
            }
        }
        if (logger.isInfoEnabled()) {
            logger.info("config locations: " + configLocations);
        }
        return configLocations.toArray(new String[0]);
    }

    /**
     * 是否是需要不载入的spring配置
     *
     * @param url                      文件地址
     * @param exclusionConfigeNameList 需要排除的配置文件名称
     * @return 不载入则返回true
     */
    private boolean isExclusionConfig(String url, Set<String> exclusionConfigeNameList) {
        for (String exclusionConfigeName : exclusionConfigeNameList) {
            if (url.contains(exclusionConfigeName)) {
                return true;
            }
        }
        return false;
    }

    private class JApplicationContextDisposable implements JDisposable {
        private Closeable closeable;

        public JApplicationContextDisposable(Closeable closeable) {
            this.closeable = closeable;
        }

        @Override
        public void destroy() {
            try {
                this.closeable.close();
            } catch (IOException e) {
                throw new JModuleLinkException("close application context has error.", e);
            }
        }

    }
}